﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Timers;
using System.Xml;
using System.Text;

namespace Logic
{
    //Defines one actor entity in the game
    class Actor
    {
        //General 
        public string Name { get; set; }
        public Traits Stats { get; set; }
        public ActorMessageQueue MessageQueue { get; set; }
        public Actor Target { get; set; }
        private Timer learnTimer { get; set; }

        //Combat
        public Point Position { get; set; }

        public Stances Stance { get; set; }
        public enum Stances
        {
            Attacking,
            Moving,
            Blocking,
            Casting,
            Idle,
            Dead
        }

        //ctor
        public Actor(string name, Traits stats)
        {
            this.Name = name;
            this.Stats = stats;
            this.MessageQueue = new ActorMessageQueue(this.Name);
            this.MessageQueue.QueueChanged += MessageQueue_QueueChanged;

            learnTimer = new Timer(3000.00);
            learnTimer.Elapsed += learnTimer_Elapsed;

            this.Stance = Stances.Idle;
            this.Disable();
        }

        void learnTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            //learnTimer.Stop();
            Learn();
            //learnTimer.Start();
        }

        public void Enable() 
        {
            this.MessageQueue.Start();
            this.learnTimer.Start();
        }

        public void Disable()
        {
            this.MessageQueue.Stop();
            this.learnTimer.Stop();
        }

        public Actor ActorFromXml(string path)
        {
            var newActor = new Actor(this.Name, new Traits());
            newActor.Disable();

            var xmlDoc = new XmlDocument();
            xmlDoc.Load(path);

            newActor.Name = xmlDoc.DocumentElement.Attributes["name"].Value;

            var traitsNodes = xmlDoc.SelectNodes("//actor/traits/*");

            foreach (XmlNode item in traitsNodes)
            {
                var type = item.Attributes["type"].Value;
                var name = item.Attributes[0].Name;
                if (type == "int")
                {
                    if (name == "armor") { newActor.Stats.Armor = Convert.ToInt32(item.Attributes["armor"].Value); }
                    if (name == "range") { newActor.Stats.AutoAttackRange = Convert.ToInt32(item.Attributes["range"].Value); }
                    if (name == "autoRange") { newActor.Stats.AutoAttackAgressionRange = Convert.ToInt32(item.Attributes["autoRange"].Value); }
                    if (name == "health") { newActor.Stats.Health = Convert.ToInt32(item.Attributes["health"].Value); }
                    if (name == "criticalHealthPercentage") { newActor.Stats.CriticalHealthPercentage = Convert.ToInt32(item.Attributes["criticalHealthPercentage"].Value); }
                    if (name == "energy") { newActor.Stats.Energy = Convert.ToInt32(item.Attributes["energy"].Value); }
                    if (name == "magicResist") { newActor.Stats.MagicResistance = Convert.ToInt32(item.Attributes["magicResist"].Value); }
                    if (name == "attackDamage") { newActor.Stats.AttackDamage = Convert.ToInt32(item.Attributes["attackDamage"].Value); }
                }
                else if (type == "double")
                {
                    if (name == "attackSpeed") { newActor.Stats.AttackSpeed = Convert.ToDouble(item.Attributes["attackSpeed"].Value); }
                }
            }

            //take max health
            newActor.Stats.MaxHealth = newActor.Stats.Health;

            //newActor.MessageQueue = new ActorMessageQueue(this.Name);
            //newActor.MessageQueue.QueueChanged += MessageQueue_QueueChanged;

            //newActor.learnTimer = new Timer(100.00);
            //newActor.learnTimer.Elapsed += learnTimer_Elapsed;

            return newActor;
        }

        void MessageQueue_QueueChanged(object sender, Logic.QueueChangedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine(e.Operation + "ED Message:\n" + e.Message.ToString());
            React();
        }

        public override string ToString()
        {
            return string.Format("Name: {0}\n--Health: {1}\n--Energy: {2}\n--Armor: {3}\n--Magic Resist: {4}", Name,
                          Stats.Health, Stats.Energy, Stats.Armor, Stats.MagicResistance);
        }

        // does a series of checks and posts messages to the MQ
        // basically fills the MQ with information of what's going on
        public void Learn()
        {
            if (this.MessageQueue.State != ActorMessageQueue.States.Running)
            {
                return;
            }

            var msg = new Message();
            
            var distance = 0;
            if (Target != null)
            {
                //is target in range?
                
                if (this.Position.X < Target.Position.X)
                {
                    distance = Target.Position.X - this.Position.X;
                }
                else
                {
                    distance = this.Position.X - Target.Position.X;
                }

                if (Target.Stance == Stances.Casting)
                {
                    //write ENEMY CASTING message with spell object to MQ
                    msg = new Message { Type = "ENEMY", Info = "ENEMY_CASTING" };

                    this.MessageQueue.Add(msg);
                    this.React();
                }

                if (Target.Stance == Stances.Blocking)
                {
                    //write ENEMY BLOCKING message to MQ
                    msg = new Message { Type = "ENEMY", Info = "ENEMY_BLOCKING" };

                    this.MessageQueue.Add(msg);
                    this.React();
                }

                if (Target.Stance == Stances.Attacking)
                {
                    //write ENEMY ATTACKING message to MQ
                    var parameters = new List<object>();

                    //TODO: Disabled for debug
                    //parameters.Add(Target.Stats.AttackDamage);
                    parameters.Add(50);

                    if (this.Stats.Health > 0)
                    {
                        msg = new Message { Type = "ENEMY", Info = "ENEMY_ATTACKING", Params = parameters };

                        this.MessageQueue.Add(msg);
                        this.React();
                    }
                    else                                                                //  Stops <this.target> from attacking in case <this>'s health is already ">= 0"
                    {                                                                   //  << angrydiscoox >>
                        Console.WriteLine(this.Name + " is already dead");
                        this.Target.Stance = Stances.Idle;
                        this.Stance = Stances.Idle;
                    }
                    
                    
                }
            }

            //is actor dead?
            if (this.Stats.Health <= 0)
            {
                this.Stance = Stances.Dead;
            }
            else                                                                        //  in case that <this> got its health resetted and doesn't do anything else
            {                                                                           //  sets <this> to idle    << angrydiscoox >>
                if (this.Stats.Health > 0 && this.Stance == Stances.Dead)
                {
                    this.Stance = Stances.Idle;
                }
            }

            if (this.Stance == Stances.Dead)
            {
                //write DEAD message to MQ
                msg = new Message { Type = "SELF", Info = "SELF_DEAD" };
                this.MessageQueue.Add(msg);
                this.React();
                return;
            }

            //is the health below critical amount?
            if (this.Stats.Health < (this.Stats.MaxHealth / 100) * this.Stats.CriticalHealthPercentage)
            {
                //write HP CRITICALLY LOW message to MQ
                msg = new Message { Type = "SELF", Info = "SELF_HP_CRITICAL" };
                this.MessageQueue.Add(msg);
                this.React();
            }
            
            if (distance <= this.Stats.AutoAttackRange)
            {
                //write ENEMY IN RANGE message to MQ
                msg = new Message { Type = "ENEMY", Info = "ENEMY_IN_RANGE" };
                
                //disabled for debug
                //this.MessageQueue.Add(msg);
                //this.React();
            }
            
            if (distance > this.Stats.AutoAttackRange)
            {
                //write ENEMY VISIBLE message with distance to MQ
                msg = new Message { Type = "ENEMY", Info = "ENEMY_VISIBLE" };

                this.MessageQueue.Add(msg);
                this.React();
            }
            
            

            
        }

        //called on MessageQueue_QueueChanged 
        //dequeues one message from MQ
        public void React()
        {
            var msg = this.MessageQueue.Dispatch();
            if (msg.Type == "ENEMY")
            {
                if (msg.Info == "ENEMY_ATTACKING")
                {
                    //TODO: Implement DSL insertion
                    Console.WriteLine(this.Name + ": " + Target.Name + " is attacking");
                    var damage = (int)msg.Params[0];
                    this.Stats.Health -= damage;
                    Console.WriteLine(this.Name + ": " + Target.Name + " did "+ damage +" damage");
                }
                if (msg.Info == "ENEMY_CASTING")
                {
                    //TODO: Get spell object from params

                    //TODO: Implement DSL insertion
                    Console.WriteLine(this.Name + ": " + Target.Name + " is casting");
                }
                if (msg.Info == "ENEMY_BLOCKING")
                {
                    //TODO: Implement DSL insertion
                    Console.WriteLine(this.Name + ": " + Target.Name + " is blocking");
                }

                if (msg.Info == "ENEMY_VISIBLE")
                {
                    //TODO: Implement DSL insertion
                    //Get distance from params
                    Console.WriteLine(this.Name + ": " + Target.Name + " is visible");
                }

                if (msg.Info == "ENEMY_IN_RANGE")
                {
                    //TODO: Implement DSL insertion
                    Console.WriteLine(this.Name + ": " + Target.Name + " is in range");
                }
            }
            else if (msg.Type == "SELF")
            {
                if (msg.Info == "SELF_HP_CRITICAL")
                {
                    //TODO: Implement DSL insertion
                    Console.WriteLine(this.Name + ": My health is critical");
                }

                if (msg.Info == "SELF_DEAD")
                {
                    //TODO: Implement DSL insertion
                    Console.WriteLine(this.Name + ": I am dead");
                }
            }
            else
            {
                Console.WriteLine(this.Name + ": I don't know what's going on!");
            }
        }
    }
}
